/*
 * @(#)Component.java     14/08/02
 * 
 * Author             Rick Wilson
 * Copyright (c) 2014 Rick Wilson
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of 'Taareekah' nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

package gameobject;

//~--- non-JDK imports --------------------------------------------------------

import org.codehaus.groovy.runtime.MethodClosure;

//~--- JDK imports ------------------------------------------------------------

import java.io.Serializable;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * The base class from which all components inherit.
 * @author rick
 */
public class Component implements Serializable {

    /** Required for serialization to work correctly with derived classes. 
     * Must be present in all derived classes as well. */
    private static final long	serialVersionUID	= 20140428L;

    /**
     * A list of components this component requires. Set in subclass constructors.
     */
    protected Set<String>	required_components	= new HashSet<>();

    /** The data members mapped to their names. */
    private Map<String, Object>	attributes;

    /** The member functions mapped to their names. */
    private Map<String, MethodClosure>	methods;

    /**
     * The typename of this component. Set in the constructor of inheriting classes.
     */
    protected String	component_typename;

    /**
     * A GOHandle pointing to the GO this component belongs to.
     */
    protected GOHandle	owner_handle;

    /**
     * Creates attributes and methods maps.
     */
    protected Component() {
	attributes	= new HashMap<>();
	methods		= new HashMap<>();
    }

    /**
     * Returns the typename of this component.
     * @return This component's typename.
     */
    public String getComponentTypename() {
	return component_typename;
    }

    /**
     * Adds the passed name to required_components. Should only be called in
     * a subclasses constructor.
     * @param name The required components name.
     */
    protected void addRequiredComponent(String name) {
	required_components.add(name);
    }

    /**
     * Returns the list of required components.
     * @return List of required components.
     */
    public Set<String> getRequiredComponents() {
	return required_components;
    }

    /**
     * Sets this component's GO owner. Doesn't check validity of passed handle.
     * @param handle Handle to the owner.
     */
    public void setOwner(GOHandle handle) {
	owner_handle	= handle;
    }

    /**
     * Returns a handle to this component's owner.
     * @return This component's owner.
     */
    public GOHandle getOwner() {
	return owner_handle;
    }

    /**
     * Checks if a method with name 'name' exists.
     * @param name The name of the method.
     * @return True if it exists, false otherwise.
     */
    public boolean hasMethod(String name) {
	return methods.containsKey(name);
    }

    /**
     * Returns a Set with the names of all the methods this component has.
     * @return A set of Strings with all method names.
     */
    public Set<String> getAllMethodNames() {
	return methods.keySet();
    }

    /**
     * Returns the method with name 'name' or null if it doesn't exist.
     * @param name The method name.
     * @return the method with name 'name' or null if it doesn't exist.
     */
    public MethodClosure getMethod(String name) {
	return methods.get(name);
    }

    /**
     * Removes the method with name 'name'.
     * @param name The method name.
     */
    public void removeMethod(String name) {
	methods.remove(name);
    }

    /**
     * Adds the method with key 'name'. Overwrites any previous method with the same name.
     * @param name The name of the method.
     * @param obj The actual method.
     */
    public void setMethod(String name, MethodClosure obj) {
	methods.put(name, obj);
    }

    /**
     * Creates a new MethodClosure for the method 'func_name' belonging to obj, then adds the closure.
     * @param obj The object with method 'func_name'.
     * @param func_name The name of the method on Object obj.
     */
    public void createAndAddMethodClosure(Object obj, String func_name) {
	setMethod(func_name, new MethodClosure(obj, func_name));
    }

    /**
     * Calls the method 'name' with the argument args. Does not check that the method exists.
     * @param name The method name.
     * @param args A single argument to pass.
     * @return Whatever the method returns.
     */
    public Object callMethod(String name, Object args) {
	return getMethod(name).call(args);
    }

    /**
     * Calls the method 'name' with arguments args. Does not check that method exists.
     * @param name The name of the method.
     * @param args A variable number of arguments to pass.
     * @return Whatever the method returns.
     */
    public Object callMethod(String name, Object... args) {
	return getMethod(name).call(args);
    }

    /**
     * Calls the method 'name' without arguments. Doesn't check if method exists.
     * @param name The method to call
     * @return Whatever the method returns.
     */
    public Object callMethod(String name) {
	return getMethod(name).call();
    }

    /**
     * Checks if an attribute with name 'name' exists.
     * @param name The attribute's name.
     * @return True if it exists, false otherwise.
     */
    public boolean hasAttribute(String name) {
	return attributes.containsKey(name);
    }

    /**
     * Returns the attribute with name 'name' or default_obj if attribute doesn't exist.
     * @param name The name of the attribute to retrieve.
     * @param default_obj An object to return if the attribute doesn't exist.
     * @return The attribute with name 'name' or default_obj.
     */
    public Object getAttribute(String name, Object default_obj) {
	if (hasAttribute(name)) {
	    return attributes.get(name);
	}

	return default_obj;
    }

    /**
     * Returns the attribute with name 'name'. Does not check if attribute exists.
     * @param name The attribute's name.
     * @return The attribute or null if it doesn't exist.
     */
    public Object getAttribute(String name) {
	return attributes.get(name);
    }

    /**
     * Removes that attribute with name 'name' and returns its value.
     * @param name The attribute's name
     * @return The attribute's value or null if it didn't exist.
     */
    public Object removeAttribute(String name) {
	if (!hasAttribute(name)) {
	    return null;
	}

	Object	ret	= attributes.get(name);

	attributes.remove(name);

	return ret;
    }

    /**
     * Adds a new attribute with name 'name' and value 'obj'.
     * @param name The name of the attribute.
     * @param obj The attribute's value.
     * @throws Exception If an attribute with that name already exists.
     */
    public void addAttribute(String name, Object obj) throws Exception {
	if (hasAttribute(name)) {
	    throw new Exception("Attribute already exists");
	}

	attributes.put(name, obj);
    }

    /**
     * Sets the value of attribute 'name' to obj.
     * @param name The attribute name
     * @param obj Its new value.
     * @throws Exception If the attribute doesn't exist.
     */
    public void setAttribute(String name, Object obj) throws Exception {
	if (!hasAttribute(name)) {
	    throw new Exception("Invalid attribute");
	}

	attributes.put(name, obj);
    }

    /**
     * Returns the names of all attributes.
     * @return The names of all attributes.
     */
    public Set<String> getAllAttributeNames() {
	return attributes.keySet();
    }

    /**
     * Returns the map of attribute names to their values.
     * @return the map of attribute names to their values.
     */
    public Map<String, Object> getAttributesMap() {
	return attributes;
    }
}


//~ Formatted by Jindent --- http://www.jindent.com
